{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "view-in-github"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/mcks2000/llm_notebooks/blob/main/notebooks/enhancing_rag_with_graph.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5x3LkpUztHNU"
      },
      "outputs": [],
      "source": [
        "%pip install --upgrade --quiet  langchain langchain-community langchain-openai langchain-experimental neo4j wikipedia tiktoken yfiles_jupyter_graphs"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jPIRSGz4tHNV"
      },
      "outputs": [],
      "source": [
        "from langchain_core.runnables import (\n",
        "    RunnableBranch,\n",
        "    RunnableLambda,\n",
        "    RunnableParallel,\n",
        "    RunnablePassthrough,\n",
        ")\n",
        "from langchain_core.prompts import ChatPromptTemplate\n",
        "from langchain_core.prompts.prompt import PromptTemplate\n",
        "from langchain_core.pydantic_v1 import BaseModel, Field\n",
        "from typing import Tuple, List, Optional\n",
        "from langchain_core.messages import AIMessage, HumanMessage\n",
        "from langchain_core.output_parsers import StrOutputParser\n",
        "import os\n",
        "from langchain_community.graphs import Neo4jGraph\n",
        "from langchain.document_loaders import WikipediaLoader\n",
        "from langchain.text_splitter import TokenTextSplitter\n",
        "from langchain_openai import ChatOpenAI\n",
        "from langchain_experimental.graph_transformers import LLMGraphTransformer\n",
        "from neo4j import GraphDatabase\n",
        "from yfiles_jupyter_graphs import GraphWidget\n",
        "from langchain_community.vectorstores import Neo4jVector\n",
        "from langchain_openai import OpenAIEmbeddings\n",
        "from langchain_community.vectorstores.neo4j_vector import remove_lucene_chars\n",
        "from langchain_core.runnables import ConfigurableField, RunnableParallel, RunnablePassthrough\n",
        "\n",
        "try:\n",
        "  import google.colab\n",
        "  from google.colab import output\n",
        "  output.enable_custom_widget_manager()\n",
        "except:\n",
        "  pass"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-KeAfaMQ0VKh"
      },
      "source": [
        "# Enhancing RAG-based applications accuracy by constructing and leveraging knowledge graphs\n",
        "## A practical guide to constructing and retrieving information from knowledge graphs in RAG applications with Neo4j and LangChain\n",
        "\n",
        "Graph retrieval augmented generation (Graph RAG) is gaining momentum and emerging as a powerful addition to traditional vector search retrieval methods. This approach leverages the structured nature of graph databases, which organize data as nodes and relationships, to enhance the depth and contextuality of retrieved information.\n",
        "\n",
        "Graphs are great at representing and storing heterogeneous and interconnected information in a structured manner, effortlessly capturing complex relationships and attributes across diverse data types. In contrast, vector databases often struggle with such structured information, as their strength lies in handling unstructured data through high-dimensional vectors. In your RAG application, you can combine structured graph data with vector search through unstructured text to achieve the best of both worlds, which is exactly what we will do in this blog post.\n",
        "\n",
        "Knowledge graphs are great, but how do you create one? Constructing a knowledge graph is typically the most challenging step in leveraging the power of graph-based data representation. It involves gathering and structuring the data, which requires a deep understanding of both the domain and graph modeling. To simplify this process, we have been experimenting with LLMs. LLMs, with their profound understanding of language and context, can automate significant parts of the knowledge graph creation process. By analyzing text data, these models can identify entities, understand the relationships between them, and suggest how they might be best represented in a graph structure. As a result of these experiments, we have added the first version of the graph construction module to LangChain, which we will demonstrate in this blog post.\n",
        "\n",
        "## Neo4j Environment Setup\n",
        "\n",
        "You need to set up a Neo4j instance follow along with the examples in this blog post. The easiest way is to start a free instance on [Neo4j Aura](https://neo4j.com/cloud/platform/aura-graph-database/), which offers cloud instances of Neo4j database. Alternatively, you can also set up a local instance of the Neo4j database by downloading the Neo4j Desktop application and creating a local database instance."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "L0nXP1aYtHNW"
      },
      "outputs": [],
      "source": [
        "os.environ[\"OPENAI_API_KEY\"] = \"sk-\"\n",
        "os.environ[\"NEO4J_URI\"] = \"bolt://18.212.197.205:7687\"\n",
        "os.environ[\"NEO4J_USERNAME\"] = \"neo4j\"\n",
        "os.environ[\"NEO4J_PASSWORD\"] = \"tendencies-meanings-enlistment\"\n",
        "\n",
        "graph = Neo4jGraph()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GHKExwYntyD9"
      },
      "source": [
        "Additionally, you must provide an [OpenAI key](https://openai.com/), as we will use their models in this blog post.\n",
        "## Data ingestion\n",
        "For this demonstration, we will use Elizabeth I's Wikipedia page. We can utilize [LangChain loaders](https://python.langchain.com/docs/modules/data_connection/document_loaders/![graphhybrid.png]()) to fetch and split the documents from Wikipedia seamlessly."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "sGhtLTAStHNW",
        "outputId": "39f65561-6c6b-4370-a51f-1d8118591158"
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "/usr/local/lib/python3.10/dist-packages/wikipedia/wikipedia.py:389: GuessedAtParserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system (\"lxml\"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.\n",
            "\n",
            "The code that caused this warning is on line 389 of the file /usr/local/lib/python3.10/dist-packages/wikipedia/wikipedia.py. To get rid of this warning, pass the additional argument 'features=\"lxml\"' to the BeautifulSoup constructor.\n",
            "\n",
            "  lis = BeautifulSoup(html).find_all('li')\n"
          ]
        }
      ],
      "source": [
        "# Read the wikipedia article\n",
        "raw_documents = WikipediaLoader(query=\"Elizabeth I\").load()\n",
        "# Define chunking strategy\n",
        "text_splitter = TokenTextSplitter(chunk_size=512, chunk_overlap=24)\n",
        "documents = text_splitter.split_documents(raw_documents[:3])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kphZMjjVuGAM"
      },
      "source": [
        "Now it's time to construct a graph based on the retrieved documents. For this purpose, we have implemented an `LLMGraphTransformermodule` that significantly simplifies constructing and storing a knowledge graph in a graph database."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pXf7OTGHtHNW"
      },
      "outputs": [],
      "source": [
        "llm=ChatOpenAI(temperature=0, model_name=\"gpt-3.5-turbo-0125\") # gpt-4-0125-preview occasionally has issues\n",
        "llm_transformer = LLMGraphTransformer(llm=llm)\n",
        "\n",
        "graph_documents = llm_transformer.convert_to_graph_documents(documents)\n",
        "graph.add_graph_documents(\n",
        "    graph_documents,\n",
        "    baseEntityLabel=True,\n",
        "    include_source=True\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ll2asQiAugSW"
      },
      "source": [
        "You can define which LLM you want the knowledge graph generation chain to use. At the moment, we support only function calling models from OpenAI and Mistral. However, we plan to expand the LLM selection in the future. In this example, we are using the latest GPT-4. Note that the quality of generated graph significantly depends on the model you are using. In theory, you always want to use the most capable one. The LLM graph transformers returns graph documents, which can be imported to Neo4j via the `add_graph_documents` method. The `baseEntityLabel` parameter assigns an additional `__Entity__` label to each node, enhancing indexing and query performance. The `include_source` parameter links nodes to their originating documents, facilitating data traceability and context understanding.\n",
        "\n",
        "You can inspect the generated graph with yfiles visualization."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 817,
          "referenced_widgets": [
            "8e37edd9789a4d57a7be401628e7ff7f",
            "9bac7003afd84cecb4e67a81a396ec8d"
          ]
        },
        "id": "RMZlhtDmtHNW",
        "outputId": "86efa842-3297-45d6-dab2-681bbc836b4d"
      },
      "outputs": [
        {
          "data": {
            "application/vnd.jupyter.widget-view+json": {
              "model_id": "8e37edd9789a4d57a7be401628e7ff7f",
              "version_major": 2,
              "version_minor": 0
            },
            "text/plain": [
              "GraphWidget(layout=Layout(height='800px', width='100%'))"
            ]
          },
          "metadata": {
            "application/vnd.jupyter.widget-view+json": {
              "colab": {
                "custom_widget_manager": {
                  "url": "https://ssl.gstatic.com/colaboratory-static/widgets/colab-cdn-widget-manager/2b70e893a8ba7c0f/manager.min.js"
                }
              }
            }
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "# directly show the graph resulting from the given Cypher query\n",
        "default_cypher = \"MATCH (s)-[r:!MENTIONS]->(t) RETURN s,r,t LIMIT 50\"\n",
        "\n",
        "def showGraph(cypher: str = default_cypher):\n",
        "    # create a neo4j session to run queries\n",
        "    driver = GraphDatabase.driver(\n",
        "        uri = os.environ[\"NEO4J_URI\"],\n",
        "        auth = (os.environ[\"NEO4J_USERNAME\"],\n",
        "                os.environ[\"NEO4J_PASSWORD\"]))\n",
        "    session = driver.session()\n",
        "    widget = GraphWidget(graph = session.run(cypher).graph())\n",
        "    widget.node_label_mapping = 'id'\n",
        "    #display(widget)\n",
        "    return widget\n",
        "\n",
        "showGraph()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1guHjU4uyEZK"
      },
      "source": [
        "## Hybrid Retrieval for RAG\n",
        "After the graph generation, we will use a hybrid retrieval approach that combines vector and keyword indexes with graph retrieval for RAG applications.\n",
        "\n",
        "![retrieval](https://raw.githubusercontent.com/tomasonjo/blogs/master/graphhybrid.png)\n",
        "\n",
        "The diagram illustrates a retrieval process beginning with a user posing a question, which is then directed to an RAG retriever. This retriever employs keyword and vector searches to search through unstructured text data and combines it with the information it collects from the knowledge graph. Since Neo4j features both keyword and vector indexes, you can implement all three retrieval options with a single database system. The collected data from these sources is fed into an LLM to generate and deliver the final answer.\n",
        "## Unstructured data retriever\n",
        "You can use the Neo4jVector.from_existing_graph method to add both keyword and vector retrieval to documents. This method configures keyword and vector search indexes for a hybrid search approach, targeting nodes labeled Document. Additionally, it calculates text embedding values if they are missing.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "GHbJPMfDtHNW"
      },
      "outputs": [],
      "source": [
        "vector_index = Neo4jVector.from_existing_graph(\n",
        "    OpenAIEmbeddings(),\n",
        "    search_type=\"hybrid\",\n",
        "    node_label=\"Document\",\n",
        "    text_node_properties=[\"text\"],\n",
        "    embedding_node_property=\"embedding\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2nzfPwvvy0Yz"
      },
      "source": [
        "The vector index can then be called with the similarity_search method.\n",
        "## Graph retriever\n",
        "On the other hand, configuring a graph retrieval is more involved but offers more freedom. In this example, we will use a full-text index to identify relevant nodes and then return their direct neighborhood.\n",
        "\n",
        "![graph](https://raw.githubusercontent.com/tomasonjo/blogs/master/neighbor.png)\n",
        "\n",
        "The graph retriever starts by identifying relevant entities in the input. For simplicity, we instruct the LLM to identify people, organizations, and locations. To achieve this, we will use LCEL with the newly added `with_structured_output` method to achieve this."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6yCMz_sRtHNW",
        "outputId": "f533f279-9a2b-48d6-830b-28d04c43550b"
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "/usr/local/lib/python3.10/dist-packages/langchain_core/_api/beta_decorator.py:86: LangChainBetaWarning: The function `with_structured_output` is in beta. It is actively being worked on, so the API may change.\n",
            "  warn_beta(\n"
          ]
        }
      ],
      "source": [
        "# Retriever\n",
        "\n",
        "graph.query(\n",
        "    \"CREATE FULLTEXT INDEX entity IF NOT EXISTS FOR (e:__Entity__) ON EACH [e.id]\")\n",
        "\n",
        "# Extract entities from text\n",
        "class Entities(BaseModel):\n",
        "    \"\"\"Identifying information about entities.\"\"\"\n",
        "\n",
        "    names: List[str] = Field(\n",
        "        ...,\n",
        "        description=\"All the person, organization, or business entities that \"\n",
        "        \"appear in the text\",\n",
        "    )\n",
        "\n",
        "prompt = ChatPromptTemplate.from_messages(\n",
        "    [\n",
        "        (\n",
        "            \"system\",\n",
        "            \"You are extracting organization and person entities from the text.\",\n",
        "        ),\n",
        "        (\n",
        "            \"human\",\n",
        "            \"Use the given format to extract information from the following \"\n",
        "            \"input: {question}\",\n",
        "        ),\n",
        "    ]\n",
        ")\n",
        "\n",
        "entity_chain = prompt | llm.with_structured_output(Entities)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "n-Cs7RFAzdT3"
      },
      "source": [
        "Let's test it out:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "54H15KNAtHNX",
        "outputId": "236df5a6-9f9e-49fd-a360-e4bb51dc7288"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "['Amelia Earhart']"
            ]
          },
          "execution_count": 9,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "entity_chain.invoke({\"question\": \"Where was Amelia Earhart born?\"}).names"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e2S2aWq5zfQO"
      },
      "source": [
        "Great, now that we can detect entities in the question, let's use a full-text index to map them to the knowledge graph. First, we need to define a full-text index and a function that will generate full-text queries that allow a bit of misspelling, which we won't go into much detail here."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dY8huoM8tHNX"
      },
      "outputs": [],
      "source": [
        "def generate_full_text_query(input: str) -> str:\n",
        "    \"\"\"\n",
        "    Generate a full-text search query for a given input string.\n",
        "\n",
        "    This function constructs a query string suitable for a full-text search.\n",
        "    It processes the input string by splitting it into words and appending a\n",
        "    similarity threshold (~2 changed characters) to each word, then combines\n",
        "    them using the AND operator. Useful for mapping entities from user questions\n",
        "    to database values, and allows for some misspelings.\n",
        "    \"\"\"\n",
        "    full_text_query = \"\"\n",
        "    words = [el for el in remove_lucene_chars(input).split() if el]\n",
        "    for word in words[:-1]:\n",
        "        full_text_query += f\" {word}~2 AND\"\n",
        "    full_text_query += f\" {words[-1]}~2\"\n",
        "    return full_text_query.strip()\n",
        "\n",
        "# Fulltext index query\n",
        "def structured_retriever(question: str) -> str:\n",
        "    \"\"\"\n",
        "    Collects the neighborhood of entities mentioned\n",
        "    in the question\n",
        "    \"\"\"\n",
        "    result = \"\"\n",
        "    entities = entity_chain.invoke({\"question\": question})\n",
        "    for entity in entities.names:\n",
        "        response = graph.query(\n",
        "            \"\"\"CALL db.index.fulltext.queryNodes('entity', $query, {limit:2})\n",
        "            YIELD node,score\n",
        "            CALL {\n",
        "              MATCH (node)-[r:!MENTIONS]->(neighbor)\n",
        "              RETURN node.id + ' - ' + type(r) + ' -> ' + neighbor.id AS output\n",
        "              UNION\n",
        "              MATCH (node)<-[r:!MENTIONS]-(neighbor)\n",
        "              RETURN neighbor.id + ' - ' + type(r) + ' -> ' +  node.id AS output\n",
        "            }\n",
        "            RETURN output LIMIT 50\n",
        "            \"\"\",\n",
        "            {\"query\": generate_full_text_query(entity)},\n",
        "        )\n",
        "        result += \"\\n\".join([el['output'] for el in response])\n",
        "    return result"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "g-F9BjghzjdH"
      },
      "source": [
        "The `structured_retriever` function starts by detecting entities in the user question. Next, it iterates over the detected entities and uses a Cypher template to retrieve the neighborhood of relevant nodes. Let's test it out!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "_6fOJRPntHNX",
        "outputId": "a99ffca0-2d4d-4374-8519-c6e37c395f1f"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Elizabeth I - RULED -> England\n",
            "Elizabeth I - RULED -> Ireland\n",
            "Elizabeth I - BELONGED_TO -> House Of Tudor\n",
            "Elizabeth I - PARENT -> Henry Viii\n",
            "Elizabeth I - PARENT -> Anne Boleyn\n",
            "Henry Viii - SPOUSE -> Anne Boleyn\n",
            "Henry Viii - PARENT -> Edward Vi\n",
            "Edward Vi - BEQUEATHED_CROWN_TO -> Lady Jane Grey\n",
            "Edward Vi - IGNORED_CLAIMS_OF -> Mary\n",
            "Edward Vi - IGNORED_CLAIMS_OF -> Elizabeth I\n",
            "Mary - IMPRISONED -> Elizabeth I\n",
            "Elizabeth I - DEPENDED_ON -> William Cecil\n",
            "Elizabeth I - CREATED_TITLE -> Baron Burghley\n",
            "Elizabeth I - SUCCEEDED_BY -> James Vi Of Scotland\n",
            "Mary, Queen Of Scots - PARENT -> James Vi Of Scotland\n",
            "Elizabeth I - DEPENDED_ON -> Francis Walsingham\n",
            "Elizabeth - WAR -> Spain\n",
            "Elizabeth - WAR -> Netherlands\n",
            "Elizabeth - WAR -> France\n",
            "Elizabeth - WAR -> Ireland\n",
            "Elizabeth - LEAD -> William Shakespeare\n",
            "Elizabeth - LEAD -> Christopher Marlowe\n",
            "Elizabeth - LEAD -> Francis Drake\n",
            "Elizabeth - LEAD -> Walter Raleigh\n",
            "Elizabeth - DEFEAT -> Spanish Armada\n",
            "Elizabeth I - SUCCESSOR -> James Vi Of Scotland\n",
            "Henry Viii - SUCCESSOR -> Edward Vi\n",
            "Henry Viii - SUCCESSOR -> Mary I\n",
            "Henry Viii - SUCCESSOR -> Elizabeth I\n",
            "Henry Viii - SUCCESSOR -> Jane Grey\n",
            "Henry Viii - SUCCESSOR -> Katherine Grey\n",
            "Henry Viii - SUCCESSOR -> Mary Grey\n",
            "Henry Viii - SUCCESSOR -> Margaret Clifford\n",
            "Mary I - PREFERRED_SUCCESSOR -> Margaret Douglas\n",
            "Margaret Douglas - FAMILY_RELATION -> Henry Stuart, Lord Darnley\n",
            "Henry Stuart, Lord Darnley - MARRIAGE -> Mary, Queen Of Scots\n",
            "Margaret Tudor - GRANDPARENT -> James Vi\n",
            "Margaret Tudor - GRANDPARENT -> Arbella Stuart\n",
            "Margaret Douglas - PARENT -> Charles Stuart\n",
            "Charles Stuart - PARENT -> Arbella Stuart\n",
            "Mary, Queen Of Scots - PARENT -> James Vi\n",
            "Mary, Queen Of Scots - CONTENDER -> Elizabeth\n",
            "Frances Grey - PARENT -> Lady Jane Grey\n",
            "Frances Grey - PARENT -> Lady Catherine Grey\n",
            "Frances Grey - PARENT -> Lady Mary Grey\n",
            "Charles Brandon - SPOUSE -> Frances Grey\n",
            "Charles Brandon - SPOUSE -> Eleanor Clifford\n",
            "Henry Grey - SPOUSE -> Frances Grey\n",
            "Lady Catherine Grey - SPOUSE -> Henry Herbert\n",
            "Elizabeth Petrovna - HELD_TITLE -> Empress Of Russia\n"
          ]
        }
      ],
      "source": [
        "print(structured_retriever(\"Who is Elizabeth I?\"))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xN9c_dEozyaO"
      },
      "source": [
        "## Final retriever\n",
        "As we mentioned at the start, we'll combine the unstructured and graph retriever to create the final context that will be passed to an LLM."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iCTMp3prtHNX"
      },
      "outputs": [],
      "source": [
        "def retriever(question: str):\n",
        "    print(f\"Search query: {question}\")\n",
        "    structured_data = structured_retriever(question)\n",
        "    unstructured_data = [el.page_content for el in vector_index.similarity_search(question)]\n",
        "    final_data = f\"\"\"Structured data:\n",
        "{structured_data}\n",
        "Unstructured data:\n",
        "{\"#Document \". join(unstructured_data)}\n",
        "    \"\"\"\n",
        "    return final_data"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NZG9Q8Ohz3Hn"
      },
      "source": [
        "As we are dealing with Python, we can simply concatenate the outputs using the f-string.\n",
        "## Defining the RAG chain\n",
        "We have successfully implemented the retrieval component of the RAG. First, we will introduce the query rewriting part that allows conversational follow up questions.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vu68Z79ttHNX"
      },
      "outputs": [],
      "source": [
        "# Condense a chat history and follow-up question into a standalone question\n",
        "_template = \"\"\"Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question,\n",
        "in its original language.\n",
        "Chat History:\n",
        "{chat_history}\n",
        "Follow Up Input: {question}\n",
        "Standalone question:\"\"\"  # noqa: E501\n",
        "CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template)\n",
        "\n",
        "def _format_chat_history(chat_history: List[Tuple[str, str]]) -> List:\n",
        "    buffer = []\n",
        "    for human, ai in chat_history:\n",
        "        buffer.append(HumanMessage(content=human))\n",
        "        buffer.append(AIMessage(content=ai))\n",
        "    return buffer\n",
        "\n",
        "_search_query = RunnableBranch(\n",
        "    # If input includes chat_history, we condense it with the follow-up question\n",
        "    (\n",
        "        RunnableLambda(lambda x: bool(x.get(\"chat_history\"))).with_config(\n",
        "            run_name=\"HasChatHistoryCheck\"\n",
        "        ),  # Condense follow-up question and chat into a standalone_question\n",
        "        RunnablePassthrough.assign(\n",
        "            chat_history=lambda x: _format_chat_history(x[\"chat_history\"])\n",
        "        )\n",
        "        | CONDENSE_QUESTION_PROMPT\n",
        "        | ChatOpenAI(temperature=0)\n",
        "        | StrOutputParser(),\n",
        "    ),\n",
        "    # Else, we have no chat history, so just pass through the question\n",
        "    RunnableLambda(lambda x : x[\"question\"]),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CsH90hbvz_aF"
      },
      "source": [
        "Next, we introduce a prompt that leverages the context provided by the integrated hybrid retriever to produce the response, completing the implementation of the RAG chain."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Dzb2jcittHNY"
      },
      "outputs": [],
      "source": [
        "template = \"\"\"Answer the question based only on the following context:\n",
        "{context}\n",
        "\n",
        "Question: {question}\n",
        "Use natural language and be concise.\n",
        "Answer:\"\"\"\n",
        "prompt = ChatPromptTemplate.from_template(template)\n",
        "\n",
        "chain = (\n",
        "    RunnableParallel(\n",
        "        {\n",
        "            \"context\": _search_query | retriever,\n",
        "            \"question\": RunnablePassthrough(),\n",
        "        }\n",
        "    )\n",
        "    | prompt\n",
        "    | llm\n",
        "    | StrOutputParser()\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "w3SeRw0L0Gy3"
      },
      "source": [
        "Finally, we can go ahead and test our hybrid RAG implementation."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 53
        },
        "id": "dtU0iMNgtHNY",
        "outputId": "bf3cf94c-f030-41b4-fb3b-356c5bff98f5"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Search query: Which house did Elizabeth I belong to?\n"
          ]
        },
        {
          "data": {
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            },
            "text/plain": [
              "'Elizabeth I belonged to the House of Tudor.'"
            ]
          },
          "execution_count": 15,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "chain.invoke({\"question\": \"Which house did Elizabeth I belong to?\"})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aLjhppwj0Jz_"
      },
      "source": [
        "Let's test a follow up question!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 53
        },
        "id": "ln_Hz2obtHNY",
        "outputId": "1f510244-0406-4748-d5f7-71c3e56de9ab"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Search query: When was Elizabeth I born?\n"
          ]
        },
        {
          "data": {
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            },
            "text/plain": [
              "'Elizabeth I was born on 7 September 1533.'"
            ]
          },
          "execution_count": 16,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "chain.invoke(\n",
        "    {\n",
        "        \"question\": \"When was she born?\",\n",
        "        \"chat_history\": [(\"Which house did Elizabeth I belong to?\", \"House Of Tudor\")],\n",
        "    }\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "CvaTiHtNtHNY"
      },
      "outputs": [],
      "source": []
    }
  ],
  "metadata": {
    "colab": {
      "include_colab_link": true,
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.5"
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "8e37edd9789a4d57a7be401628e7ff7f": {
          "model_module": "yfiles-jupyter-graphs",
          "model_module_version": "^1.6.1",
          "model_name": "GraphModel",
          "state": {
            "_context_pane_mapping": [
              {
                "id": "Neighborhood",
                "title": "Neighborhood"
              },
              {
                "id": "Data",
                "title": "Data"
              },
              {
                "id": "Search",
                "title": "Search"
              },
              {
                "id": "About",
                "title": "About"
              }
            ],
            "_data_importer": "neo4j",
            "_directed": true,
            "_dom_classes": [],
            "_edges": [
              {
                "color": "#9C27B0",
                "directed": true,
                "end": 2,
                "id": 14,
                "label": "RULED",
                "properties": {
                  "label": "RULED"
                },
                "start": 1,
                "thickness_factor": 1
              },
              {
                "color": "#9C27B0",
                "directed": true,
                "end": 3,
                "id": 15,
                "label": "RULED",
                "properties": {
                  "label": "RULED"
                },
                "start": 1,
                "thickness_factor": 1
              },
              {
                "color": "#2196F3",
                "directed": true,
                "end": 4,
                "id": 16,
                "label": "BELONGED_TO",
                "properties": {
                  "label": "BELONGED_TO"
                },
                "start": 1,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 5,
                "id": 17,
                "label": "PARENT",
                "properties": {
                  "label": "PARENT"
                },
                "start": 1,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 6,
                "id": 18,
                "label": "PARENT",
                "properties": {
                  "label": "PARENT"
                },
                "start": 1,
                "thickness_factor": 1
              },
              {
                "color": "#F44336",
                "directed": true,
                "end": 6,
                "id": 19,
                "label": "SPOUSE",
                "properties": {
                  "label": "SPOUSE"
                },
                "start": 5,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 7,
                "id": 20,
                "label": "PARENT",
                "properties": {
                  "label": "PARENT"
                },
                "start": 5,
                "thickness_factor": 1
              },
              {
                "color": "#607D8B",
                "directed": true,
                "end": 8,
                "id": 21,
                "label": "BEQUEATHED_CROWN_TO",
                "properties": {
                  "label": "BEQUEATHED_CROWN_TO"
                },
                "start": 7,
                "thickness_factor": 1
              },
              {
                "color": "#673AB7",
                "directed": true,
                "end": 9,
                "id": 22,
                "label": "IGNORED_CLAIMS_OF",
                "properties": {
                  "label": "IGNORED_CLAIMS_OF"
                },
                "start": 7,
                "thickness_factor": 1
              },
              {
                "color": "#673AB7",
                "directed": true,
                "end": 1,
                "id": 23,
                "label": "IGNORED_CLAIMS_OF",
                "properties": {
                  "label": "IGNORED_CLAIMS_OF"
                },
                "start": 7,
                "thickness_factor": 1
              },
              {
                "color": "#CDDC39",
                "directed": true,
                "end": 1,
                "id": 24,
                "label": "IMPRISONED",
                "properties": {
                  "label": "IMPRISONED"
                },
                "start": 9,
                "thickness_factor": 1
              },
              {
                "color": "#9E9E9E",
                "directed": true,
                "end": 10,
                "id": 25,
                "label": "DEPENDED_ON",
                "properties": {
                  "label": "DEPENDED_ON"
                },
                "start": 1,
                "thickness_factor": 1
              },
              {
                "color": "#9C27B0",
                "directed": true,
                "end": 11,
                "id": 26,
                "label": "CREATED_TITLE",
                "properties": {
                  "label": "CREATED_TITLE"
                },
                "start": 1,
                "thickness_factor": 1
              },
              {
                "color": "#2196F3",
                "directed": true,
                "end": 12,
                "id": 27,
                "label": "SUCCEEDED_BY",
                "properties": {
                  "label": "SUCCEEDED_BY"
                },
                "start": 1,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 12,
                "id": 28,
                "label": "PARENT",
                "properties": {
                  "label": "PARENT"
                },
                "start": 13,
                "thickness_factor": 1
              },
              {
                "color": "#9E9E9E",
                "directed": true,
                "end": 14,
                "id": 29,
                "label": "DEPENDED_ON",
                "properties": {
                  "label": "DEPENDED_ON"
                },
                "start": 1,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 17,
                "id": 40,
                "label": "WAR",
                "properties": {
                  "label": "WAR"
                },
                "start": 16,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 18,
                "id": 41,
                "label": "WAR",
                "properties": {
                  "label": "WAR"
                },
                "start": 16,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 19,
                "id": 42,
                "label": "WAR",
                "properties": {
                  "label": "WAR"
                },
                "start": 16,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 3,
                "id": 43,
                "label": "WAR",
                "properties": {
                  "label": "WAR"
                },
                "start": 16,
                "thickness_factor": 1
              },
              {
                "color": "#F44336",
                "directed": true,
                "end": 20,
                "id": 44,
                "label": "LEAD",
                "properties": {
                  "label": "LEAD"
                },
                "start": 16,
                "thickness_factor": 1
              },
              {
                "color": "#F44336",
                "directed": true,
                "end": 21,
                "id": 45,
                "label": "LEAD",
                "properties": {
                  "label": "LEAD"
                },
                "start": 16,
                "thickness_factor": 1
              },
              {
                "color": "#F44336",
                "directed": true,
                "end": 22,
                "id": 46,
                "label": "LEAD",
                "properties": {
                  "label": "LEAD"
                },
                "start": 16,
                "thickness_factor": 1
              },
              {
                "color": "#F44336",
                "directed": true,
                "end": 23,
                "id": 47,
                "label": "LEAD",
                "properties": {
                  "label": "LEAD"
                },
                "start": 16,
                "thickness_factor": 1
              },
              {
                "color": "#607D8B",
                "directed": true,
                "end": 24,
                "id": 48,
                "label": "DEFEAT",
                "properties": {
                  "label": "DEFEAT"
                },
                "start": 16,
                "thickness_factor": 1
              },
              {
                "color": "#673AB7",
                "directed": true,
                "end": 12,
                "id": 64,
                "label": "SUCCESSOR",
                "properties": {
                  "label": "SUCCESSOR"
                },
                "start": 1,
                "thickness_factor": 1
              },
              {
                "color": "#673AB7",
                "directed": true,
                "end": 7,
                "id": 65,
                "label": "SUCCESSOR",
                "properties": {
                  "label": "SUCCESSOR"
                },
                "start": 5,
                "thickness_factor": 1
              },
              {
                "color": "#673AB7",
                "directed": true,
                "end": 29,
                "id": 66,
                "label": "SUCCESSOR",
                "properties": {
                  "label": "SUCCESSOR"
                },
                "start": 5,
                "thickness_factor": 1
              },
              {
                "color": "#673AB7",
                "directed": true,
                "end": 1,
                "id": 67,
                "label": "SUCCESSOR",
                "properties": {
                  "label": "SUCCESSOR"
                },
                "start": 5,
                "thickness_factor": 1
              },
              {
                "color": "#673AB7",
                "directed": true,
                "end": 30,
                "id": 68,
                "label": "SUCCESSOR",
                "properties": {
                  "label": "SUCCESSOR"
                },
                "start": 5,
                "thickness_factor": 1
              },
              {
                "color": "#673AB7",
                "directed": true,
                "end": 31,
                "id": 69,
                "label": "SUCCESSOR",
                "properties": {
                  "label": "SUCCESSOR"
                },
                "start": 5,
                "thickness_factor": 1
              },
              {
                "color": "#673AB7",
                "directed": true,
                "end": 32,
                "id": 70,
                "label": "SUCCESSOR",
                "properties": {
                  "label": "SUCCESSOR"
                },
                "start": 5,
                "thickness_factor": 1
              },
              {
                "color": "#673AB7",
                "directed": true,
                "end": 33,
                "id": 71,
                "label": "SUCCESSOR",
                "properties": {
                  "label": "SUCCESSOR"
                },
                "start": 5,
                "thickness_factor": 1
              },
              {
                "color": "#CDDC39",
                "directed": true,
                "end": 34,
                "id": 72,
                "label": "PREFERRED_SUCCESSOR",
                "properties": {
                  "label": "PREFERRED_SUCCESSOR"
                },
                "start": 29,
                "thickness_factor": 1
              },
              {
                "color": "#9E9E9E",
                "directed": true,
                "end": 35,
                "id": 73,
                "label": "FAMILY_RELATION",
                "properties": {
                  "label": "FAMILY_RELATION"
                },
                "start": 34,
                "thickness_factor": 1
              },
              {
                "color": "#9C27B0",
                "directed": true,
                "end": 13,
                "id": 74,
                "label": "MARRIAGE",
                "properties": {
                  "label": "MARRIAGE"
                },
                "start": 35,
                "thickness_factor": 1
              },
              {
                "color": "#2196F3",
                "directed": true,
                "end": 37,
                "id": 91,
                "label": "GRANDPARENT",
                "properties": {
                  "label": "GRANDPARENT"
                },
                "start": 39,
                "thickness_factor": 1
              },
              {
                "color": "#2196F3",
                "directed": true,
                "end": 38,
                "id": 92,
                "label": "GRANDPARENT",
                "properties": {
                  "label": "GRANDPARENT"
                },
                "start": 39,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 40,
                "id": 93,
                "label": "PARENT",
                "properties": {
                  "label": "PARENT"
                },
                "start": 34,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 38,
                "id": 94,
                "label": "PARENT",
                "properties": {
                  "label": "PARENT"
                },
                "start": 40,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 37,
                "id": 95,
                "label": "PARENT",
                "properties": {
                  "label": "PARENT"
                },
                "start": 13,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 16,
                "id": 96,
                "label": "CONTENDER",
                "properties": {
                  "label": "CONTENDER"
                },
                "start": 13,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 8,
                "id": 97,
                "label": "PARENT",
                "properties": {
                  "label": "PARENT"
                },
                "start": 41,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 46,
                "id": 98,
                "label": "PARENT",
                "properties": {
                  "label": "PARENT"
                },
                "start": 41,
                "thickness_factor": 1
              },
              {
                "color": "#4CAF50",
                "directed": true,
                "end": 47,
                "id": 99,
                "label": "PARENT",
                "properties": {
                  "label": "PARENT"
                },
                "start": 41,
                "thickness_factor": 1
              },
              {
                "color": "#F44336",
                "directed": true,
                "end": 41,
                "id": 100,
                "label": "SPOUSE",
                "properties": {
                  "label": "SPOUSE"
                },
                "start": 44,
                "thickness_factor": 1
              },
              {
                "color": "#F44336",
                "directed": true,
                "end": 42,
                "id": 101,
                "label": "SPOUSE",
                "properties": {
                  "label": "SPOUSE"
                },
                "start": 44,
                "thickness_factor": 1
              },
              {
                "color": "#F44336",
                "directed": true,
                "end": 41,
                "id": 102,
                "label": "SPOUSE",
                "properties": {
                  "label": "SPOUSE"
                },
                "start": 45,
                "thickness_factor": 1
              },
              {
                "color": "#F44336",
                "directed": true,
                "end": 48,
                "id": 103,
                "label": "SPOUSE",
                "properties": {
                  "label": "SPOUSE"
                },
                "start": 46,
                "thickness_factor": 1
              },
              {
                "color": "#F44336",
                "directed": true,
                "end": 51,
                "id": 117,
                "label": "HELD_TITLE",
                "properties": {
                  "label": "HELD_TITLE"
                },
                "start": 50,
                "thickness_factor": 1
              }
            ],
            "_graph_layout": {},
            "_highlight": [],
            "_license": {},
            "_model_module": "yfiles-jupyter-graphs",
            "_model_module_version": "^1.6.1",
            "_model_name": "GraphModel",
            "_neighborhood": {},
            "_nodes": [
              {
                "color": "#2196F3",
                "id": 1,
                "label": "Elizabeth I",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Elizabeth I",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#4CAF50",
                "id": 2,
                "label": "England",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "England",
                  "label": "Country:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#4CAF50"
              },
              {
                "color": "#4CAF50",
                "id": 3,
                "label": "Ireland",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Ireland",
                  "label": "Country:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#4CAF50"
              },
              {
                "color": "#F44336",
                "id": 4,
                "label": "House Of Tudor",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "House Of Tudor",
                  "label": "__Entity__:Royal family"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#F44336"
              },
              {
                "color": "#2196F3",
                "id": 5,
                "label": "Henry Viii",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Henry Viii",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 6,
                "label": "Anne Boleyn",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Anne Boleyn",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 7,
                "label": "Edward Vi",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Edward Vi",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 8,
                "label": "Lady Jane Grey",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Lady Jane Grey",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 9,
                "label": "Mary",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Mary",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 10,
                "label": "William Cecil",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "William Cecil",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#607D8B",
                "id": 11,
                "label": "Baron Burghley",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Baron Burghley",
                  "label": "Title:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#607D8B"
              },
              {
                "color": "#2196F3",
                "id": 12,
                "label": "James Vi Of Scotland",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "James Vi Of Scotland",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 13,
                "label": "Mary, Queen Of Scots",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Mary, Queen Of Scots",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 14,
                "label": "Francis Walsingham",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Francis Walsingham",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 16,
                "label": "Elizabeth",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Elizabeth",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#4CAF50",
                "id": 17,
                "label": "Spain",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Spain",
                  "label": "Country:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#4CAF50"
              },
              {
                "color": "#4CAF50",
                "id": 18,
                "label": "Netherlands",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Netherlands",
                  "label": "Country:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#4CAF50"
              },
              {
                "color": "#4CAF50",
                "id": 19,
                "label": "France",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "France",
                  "label": "Country:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#4CAF50"
              },
              {
                "color": "#673AB7",
                "id": 20,
                "label": "William Shakespeare",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "William Shakespeare",
                  "label": "Playwright:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#673AB7"
              },
              {
                "color": "#673AB7",
                "id": 21,
                "label": "Christopher Marlowe",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Christopher Marlowe",
                  "label": "Playwright:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#673AB7"
              },
              {
                "color": "#CDDC39",
                "id": 22,
                "label": "Francis Drake",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Francis Drake",
                  "label": "Explorer:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#CDDC39"
              },
              {
                "color": "#CDDC39",
                "id": 23,
                "label": "Walter Raleigh",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Walter Raleigh",
                  "label": "Explorer:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#CDDC39"
              },
              {
                "color": "#9E9E9E",
                "id": 24,
                "label": "Spanish Armada",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Spanish Armada",
                  "label": "Event:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#9E9E9E"
              },
              {
                "color": "#2196F3",
                "id": 29,
                "label": "Mary I",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Mary I",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 30,
                "label": "Jane Grey",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Jane Grey",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 31,
                "label": "Katherine Grey",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Katherine Grey",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 32,
                "label": "Mary Grey",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Mary Grey",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 33,
                "label": "Margaret Clifford",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Margaret Clifford",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 34,
                "label": "Margaret Douglas",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Margaret Douglas",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 35,
                "label": "Henry Stuart, Lord Darnley",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Henry Stuart, Lord Darnley",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 39,
                "label": "Margaret Tudor",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Margaret Tudor",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 37,
                "label": "James Vi",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "James Vi",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 38,
                "label": "Arbella Stuart",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Arbella Stuart",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 40,
                "label": "Charles Stuart",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Charles Stuart",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 41,
                "label": "Frances Grey",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Frances Grey",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 46,
                "label": "Lady Catherine Grey",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Lady Catherine Grey",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 47,
                "label": "Lady Mary Grey",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Lady Mary Grey",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 44,
                "label": "Charles Brandon",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Charles Brandon",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 42,
                "label": "Eleanor Clifford",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Eleanor Clifford",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 45,
                "label": "Henry Grey",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Henry Grey",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 48,
                "label": "Henry Herbert",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Henry Herbert",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#2196F3",
                "id": 50,
                "label": "Elizabeth Petrovna",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Elizabeth Petrovna",
                  "label": "Person:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#2196F3"
              },
              {
                "color": "#607D8B",
                "id": 51,
                "label": "Empress Of Russia",
                "position": [
                  0,
                  0
                ],
                "properties": {
                  "id": "Empress Of Russia",
                  "label": "Title:__Entity__"
                },
                "scale_factor": 1,
                "size": [
                  55,
                  55
                ],
                "styles": {},
                "type": "#607D8B"
              }
            ],
            "_overview": {
              "enabled": null,
              "overview_set": false
            },
            "_selected_graph": [
              [],
              []
            ],
            "_sidebar": {
              "enabled": true,
              "start_with": ""
            },
            "_view_count": null,
            "_view_module": "yfiles-jupyter-graphs",
            "_view_module_version": "^1.6.1",
            "_view_name": "GraphView",
            "layout": "IPY_MODEL_9bac7003afd84cecb4e67a81a396ec8d"
          }
        },
        "9bac7003afd84cecb4e67a81a396ec8d": {
          "model_module": "@jupyter-widgets/base",
          "model_module_version": "1.2.0",
          "model_name": "LayoutModel",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": "800px",
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": "100%"
          }
        }
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
